﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Dycox.ComponentModel;
using System.ComponentModel;
using Dycox.Validation;

namespace Dycox.Workflows
{
    [DebuggerDisplay("{Name}")]
    public class Stage : InitializableObject
    {

        public event EnterStageEventHandler Enter;

        public event ExitingStageEventHandler Exiting;

        public event StageCompletedEventHandler Exited;

        public event ModifyStageEventHandler Modifying;

        internal const char FullNameSeparator = '.';

        public Stage(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            _name = name;
        }

        public Type ContractType
        {
            get
            {
                if (_contractInfo != null)
                    return _contractInfo.Type;
                else
                    return null;
            }
            set
            {
                this.ContractInfo = new ContractInfo(value);
            }
        }

        private ContractInfo _contractInfo;

        internal ContractInfo ContractInfo
        {
            get { return _contractInfo; }
            set
            {
                ThrowIfNotInitializing();

                _contractInfo = value;
            }
        }


        public StageOperationContract NewContract(OperationArguments args)
        {
            return NewContract(new ContractInitArguments(args, this));
        }

        public virtual StageOperationContract NewContract(ContractInitArguments args)
        {
            if (args != null && args.Stage != this)
                throw new ArgumentException(SR.GetString("stage_not_match"));

            StageOperationContract contract = CreateContract();
            contract.InitializeNew(args);
            return contract;
        }

        public StageOperationContract GetContract(WFStep step)
        {
            if (step == null)
                throw new ArgumentNullException("step");
            if (step.StageName != this.Name)
                throw new ArgumentException(SR.GetString("stage_not_match"));

            return step.GetUserState(this);
        }

        protected virtual StageOperationContract CreateContract()
        {
            Type type = ContractType;

            if (type == null)
                return null;

            return (StageOperationContract)Activator.CreateInstance(type);
        }

        public virtual void ValidateContract(StageOperationContract contract, WorkflowExecutionContext context)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");

            if (contract.GetType() != ContractType)
                throw new ArgumentException(SR.GetString("invalid_contract_type", contract.GetType()));

            ValidationManager.Validate(contract);

            if (context is ModifyStageContext)
            {
                ModifyStageContext msc = (ModifyStageContext)context;
                ValidateContractModifying(contract, msc.OriginalState, msc);
            }
        }

        protected virtual void ValidateContractModifying(StageOperationContract current, StageOperationContract original, ModifyStageContext context)
        {
            Type type = current.GetType();

            if (type != original.GetType())
                throw new WorkflowException(SR.GetString("invalid_contract_type", original.GetType()));

            PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(type);

            foreach (PropertyDescriptor p in coll)
            {
                ModelPropertyAttribute mp = (ModelPropertyAttribute)p.Attributes[typeof(ModelPropertyAttribute)];

                if (mp != null && !mp.CanModify)
                {
                    if (!object.Equals(p.GetValue(current), p.GetValue(original)))
                        throw new WorkflowExecutionException(SR.GetString("unmodifiable_property_changed", p.Name));
                }
            }
        }

        private string _name;

        public string Name
        {
            get { return _name; }
        }

        private string _displayName;

        public string DisplayName
        {
            get { return _displayName ?? _name; }
            set
            {
                ThrowIfNotInitializing();
                _displayName = value;
            }
        }

        public string FullName
        {
            get
            {
                ValidateInWorkflow();
                return _workflow.Name + FullNameSeparator + _name;
            }
        }

        private StageProperties _properties;

        public StageProperties Properties
        {
            get
            {
                if (_properties == null)
                    _properties = new StageProperties();

                return _properties;
            }
        }

        private StageUsage _usage;

        public StageUsage Usage
        {
            get { return _usage; }
            internal set
            {
                _usage = value;
            }
        }

        private InjectionMode _mode;

        public InjectionMode InjectionMode
        {
            get { return _mode; }
            set
            {
                ThrowIfNotInitializing();
                _mode = value;
            }
        }


        private StageClass _class;

        public StageClass Class
        {
            get { return _class; }
            set
            {
                ThrowIfNotInitializing();
                _class = value;
            }
        }

        //private bool _autoStartProcess = true;

        //public bool AutoStartProcess
        //{
        //    get { return _autoStartProcess; }
        //    set
        //    {
        //        ThrowIfNotInitializing();
        //        _autoStartProcess = value;
        //    }
        //}

        private Workflow _workflow;

        public Workflow Workflow
        {
            get { return _workflow; }
            internal set { _workflow = value; }
        }

        private HintCollection _hints;

        public HintCollection Hints
        {
            get
            {
                if (_hints == null)
                    _hints = new HintCollection();
                return _hints;
            }
        }

        internal bool HasHints
        {
            get { return _hints != null && _hints.Count > 0; }
        }

        //public TransitionRule Rule
        //{
        //    get
        //    {
        //        if (_workflow == null)
        //            throw new InvalidOperationException("Stage does not belong to any workflow.");

        //        return _workflow.Rules[this];
        //    }
        //}

        private void ValidateInWorkflow()
        {
            if (_workflow == null)
                throw new InvalidOperationException("Must attach this stage to a workflow first.");

        }

        public TransitionConditionCollection EnteringCondition
        {
            get
            {
                Transition t = _workflow.Transitions[_workflow.AnyStage, this];

                if (t != null)
                    return t.Conditions;
                else
                    return null;
            }
        }

        private string _prohibitMessage;

        public string ProhibitMessage
        {
            get { return _prohibitMessage; }
            set { _prohibitMessage = value; }
        }

        public Transition[] OutgoingTransitions
        {
            get
            {
                ValidateInWorkflow();
                return _workflow.Transitions.GetTransitionsInternal(this, null, TransitionTypes.Normal).ToArray();
            }
        }

        public Stage[] OutgoingStages
        {
            get
            {
                ValidateInWorkflow();
                return _workflow.Transitions.GetOutgoingStages(this);
            }
        }

        //public EnteringConditionCollection CreateEnteringConditionCollection(string errorMessage)
        //{
        //    if (_self == null)
        //    {
        //        _self = new Transition(this);
        //        _self.SourceStage = this;
        //    }

        //    if (_enteringConditions == null) _enteringConditions = new List<EnteringConditionCollection>(2);

        //    EnteringConditionCollection coll = new EnteringConditionCollection(_self);
        //    coll.ErrorMessage = errorMessage;
        //    _enteringConditions.Add(coll);
        //    return coll;
        //}


        //StageEventProcessorCollection[] _eventCollections;

        //private StageEventProcessorCollection GetEventCollection(EventScenario scenario, bool create)
        //{
        //    if (_eventCollections == null)
        //    {
        //        if (create) _eventCollections = new StageEventProcessorCollection[Enum.GetNames(typeof(EventScenario)).Length];
        //        else return null;
        //    }

        //    StageEventProcessorCollection coll;

        //    int index = ((int)scenario);
        //    if ((coll = _eventCollections[index]) == null)
        //    {
        //        if (create)
        //        {
        //            coll = new StageEventProcessorCollection(this, scenario);
        //            _eventCollections[index] = coll;
        //        }
        //    }

        //    return coll;
        //}

        //public StageEventProcessorCollection StepCreatedEvents
        //{
        //    get
        //    {
        //        return GetEventCollection(EventScenario.StepCreated, true);
        //    }
        //}

        //public StageEventProcessorCollection StepFinishedEvents
        //{
        //    get
        //    {
        //        return GetEventCollection(EventScenario.StepFinished, true);
        //    }
        //}

        protected override void BeginInit()
        {
        }

        protected override void EndInit()
        {
        }

        protected internal virtual void OnEnter(EnterStageEventArgs e)
        {
            if (Enter != null)
                Enter(this, e);
        }

        protected internal virtual void OnExiting(ExitingStageEventArgs e)
        {
            if (Exiting != null)
                Exiting(this, e);
        }

        protected internal virtual void OnExited(StageCompletedEventArgs e)
        {
            if (Exited != null)
                Exited(this, e);
        }

        protected internal virtual void OnModifying(ModifyStageEventArgs e)
        {
            if (Modifying != null)
                Modifying(this, e);
        }

        public PropertyDescriptor[] GetDependentProperties()
        {
            List<string> names = new List<string>();

            PopulateDependentProperties(names);

            return ConditionTestArguments.ResolveProperties(names.Distinct().ToArray(), this);
        }

        protected virtual void PopulateDependentProperties(List<string> names)
        {
            foreach (var transition in this.OutgoingTransitions)
            {
                foreach (var condition in transition.Conditions)
                {
                    condition.PopulateDependentProperties(names);
                }
            }
        }

        public event EventHandler Registered;

        internal protected virtual void OnRegistered(EventArgs e)
        {
            if (Registered != null)
                Registered(this, e);
        }

        internal protected virtual DateTime GetStartTime(WFStep step)
        {
            return WorkflowManager.TimeService.Now;
        }

        internal protected virtual DateTime GetFinishTime(WFStep step)
        {
            return WorkflowManager.TimeService.Now;
        }



    }

    public enum StageUsage
    {
        Normal,
        Entrance,
        Injection,
        Placeholder
    }

    public enum StageClass
    {
        Major,
        Minor
    }
}
